{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Note\n",
    "\n",
    "Please view the [README](https://github.com/eclipse/deeplearning4j-examples/blob/master/tutorials/README.md) to learn about installing, setting up dependencies, and importing notebooks in Zeppelin"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Background\n",
    "\n",
    "In previous tutorials we learned how to configure different neural networks such as feed forward, convolutional, and recurrent networks. The type of neural network is determined by the type of hidden layers they contain. For example, feed forward neural networks are comprised of dense layers, while recurrent neural networks can include Graves LSTM (long short-term memory) layers. In this tutorial we will learn how to use combinations of different layers in a single neural network using the MultiLayerNetwork class of deeplearning4j (DL4J). Additionally, we will learn how to use preprocess our data to more efficiently train the neural networks. The MNIST dataset (images of handwritten digits) will be used as an example for a convolutional network. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "autoscroll": "auto"
   },
   "outputs": [],
   "source": [
    "import org.deeplearning4j.datasets.iterator.impl.MnistDataSetIterator\n",
    "import org.deeplearning4j.eval.Evaluation\n",
    "import org.deeplearning4j.nn.api.OptimizationAlgorithm\n",
    "import org.deeplearning4j.nn.conf.MultiLayerConfiguration\n",
    "import org.deeplearning4j.nn.conf.NeuralNetConfiguration\n",
    "import org.deeplearning4j.nn.conf.Updater\n",
    "import org.deeplearning4j.nn.multilayer.MultiLayerNetwork\n",
    "import org.deeplearning4j.nn.weights.WeightInit\n",
    "import org.deeplearning4j.nn.conf.layers.SubsamplingLayer\n",
    "import org.deeplearning4j.nn.conf.layers.ConvolutionLayer\n",
    "import org.deeplearning4j.nn.conf.inputs.InputType\n",
    "import org.deeplearning4j.eval.Evaluation\n",
    "import org.deeplearning4j.nn.conf.distribution.UniformDistribution\n",
    "import org.deeplearning4j.nn.conf.layers.{DenseLayer, OutputLayer}\n",
    "import org.deeplearning4j.nn.conf.{ComputationGraphConfiguration, MultiLayerConfiguration, NeuralNetConfiguration, Updater}\n",
    "import org.deeplearning4j.nn.multilayer.MultiLayerNetwork\n",
    "import org.deeplearning4j.nn.weights.WeightInit\n",
    "import org.nd4j.linalg.activations.Activation\n",
    "import org.nd4j.linalg.learning.config.Nesterovs\n",
    "import org.nd4j.linalg.lossfunctions.LossFunctions\n",
    "import org.nd4j.linalg.api.ndarray.INDArray\n",
    "import org.nd4j.linalg.dataset.DataSet\n",
    "import org.nd4j.linalg.lossfunctions.LossFunctions.LossFunction\n",
    "import org.nd4j.linalg.activations.Activation\n",
    "import org.nd4j.linalg.dataset.api.iterator.DataSetIterator\n",
    "import org.nd4j.linalg.dataset.api.preprocessor.DataNormalization\n",
    "import org.nd4j.linalg.dataset.api.preprocessor.ImagePreProcessingScaler\n",
    "import org.slf4j.Logger\n",
    "import org.slf4j.LoggerFactory"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Convolutional Neural Network Example\n",
    "\n",
    "Now that everything needed is imported, we can start by configuring a convolutional neural network for a MultiLayerNetwork. This network will consist of two convolutional layers, two max pooling layers, one dense layer, and an output layer. This is easy to do using DL4J's functionality; we simply add a dense layer after the max pooling layer to convert the output into vectorized form before passing it to the output layer. The neural network will then attempt to classify an observation using the vectorized data in the output layer. \n",
    "\n",
    "The only tricky part is getting the dimensions of the input to the dense layer correctly after the convolutional and max pooling layers. Note that we first start off with a 28 by 28 matrix and after applying the convolution layer with a 5 by 5 kernel we end up with twenty 24 by 24 matrices. Once the input is passed through the max pooling layer with a 2 by 2 kernel and a stride of 2 by 2, we end up with twenty 12 by 12 matrices. After the second convolutional layer with a 5 by 5 kernel, we end up with fifty 8 by 8 matrices. This output is reduced to fifty 4 by 4 matrices after the second max pooling layer which has the same kernel size and stride of the first max pooling layer. To vectorize these final matrices, we require an input of dimension 50*4*4 or 800 in the dense layer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "autoscroll": "auto"
   },
   "outputs": [],
   "source": [
    "val nChannels = 1; // Number of input channels\n",
    "val outputNum = 10; // The number of possible outcomes\n",
    "val batchSize = 64; // Test batch size\n",
    "val nEpochs = 1; // Number of training epochs\n",
    "val iterations = 1; // Number of training iterations\n",
    "val seed = 123; // Random seed\n",
    "\n",
    "val conf : MultiLayerConfiguration = new NeuralNetConfiguration.Builder()\n",
    "    .seed(12345)\n",
    "    .optimizationAlgo(OptimizationAlgorithm.STOCHASTIC_GRADIENT_DESCENT)\n",
    "    .list()\n",
    "    .layer(0, new ConvolutionLayer.Builder(5, 5)\n",
    "        .nIn(1)\n",
    "        .stride(1, 1)\n",
    "        .nOut(20)\n",
    "        .activation(Activation.IDENTITY)\n",
    "        .build())\n",
    "    .layer(1, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX)\n",
    "        .kernelSize(2,2)\n",
    "        .stride(2,2)\n",
    "        .build())\n",
    "     .layer(2, new ConvolutionLayer.Builder(5, 5)\n",
    "        .stride(1, 1)\n",
    "        .nOut(50)\n",
    "        .activation(Activation.IDENTITY)\n",
    "        .build())\n",
    "    .layer(3, new SubsamplingLayer.Builder(SubsamplingLayer.PoolingType.MAX)\n",
    "        .kernelSize(2,2)\n",
    "        .stride(2,2)\n",
    "        .build())\n",
    "    .layer(4, new DenseLayer.Builder().activation(Activation.RELU)\n",
    "        .nIn(800)\n",
    "        .nOut(500).build())\n",
    "    .layer(5, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)\n",
    "        .nIn(500)\n",
    "        .nOut(outputNum)\n",
    "        .activation(Activation.SOFTMAX)\n",
    "        .build())\n",
    "    .setInputType(InputType.convolutionalFlat(28,28,1)) \n",
    "\t.backprop(true).pretrain(false).build()\n",
    "\t\n",
    "val model = new MultiLayerNetwork(conf)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Before training the neural network, we will instantiate built-in DataSetIterators for the MNIST data. One example of data preprocessing is scaling the data. The data we are using in raw form are greyscale images, which are represented by a single matrix filled with integer values from 0 to 255. A 0 value indicates a black pixel, while a 1 value indicates a white pixel. It is helpful to scale the image pixel value from 0 to 1 instead of from 0 to 255. To do this, the ImagePreProcessingScaler class is used directly on the MnistDataSetIterators. Note that this process is typtical for data preprocessing. Once this is done, we are ready to train the neural network."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "autoscroll": "auto"
   },
   "outputs": [],
   "source": [
    "val rngSeed = 12345\n",
    "val mnistTrain = new MnistDataSetIterator(batchSize, true, rngSeed)\n",
    "val mnistTest = new MnistDataSetIterator(batchSize, false, rngSeed)\n",
    "\n",
    "val scaler : DataNormalization = new ImagePreProcessingScaler(0,1);\n",
    "scaler.fit(mnistTrain);\n",
    "mnistTrain.setPreProcessor(scaler);\n",
    "mnistTest.setPreProcessor(scaler);\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To train the neural network, we use 5 epochs or complete passes through the training set by simply calling the fit method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "autoscroll": "auto"
   },
   "outputs": [],
   "source": [
    "val nEpochs = 5\n",
    "\n",
    "(1 to nEpochs).foreach{ epoch =>\n",
    "    model.fit(mnistTrain)\n",
    "    println(\"Epoch \" + epoch + \" complete\")\n",
    "}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lastly, we use the test split of the data to evaluate how well our final model performs on data it has never seen. We can see that the model performs pretty well using only 5 epochs!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "autoscroll": "auto"
   },
   "outputs": [],
   "source": [
    "val eval : Evaluation = model.evaluate(mnistTest)\n",
    "println(eval.stats())"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Spark 2.0.0 - Scala 2.11",
   "language": "scala",
   "name": "spark2-scala"
  },
  "language_info": {
   "codemirror_mode": "text/x-scala",
   "file_extension": ".scala",
   "mimetype": "text/x-scala",
   "name": "scala",
   "pygments_lexer": "scala",
   "version": "2.11.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
